觀察 index-Start.html 發現結構非常簡單,只有一個表單內有一個輸入框<input>
及一個清單<ul>
,看樣子就是偵測輸入框輸入的字串,篩選符合的資料於清單中渲染/彩現,這次的資料並非寫死的放在程式碼中,而是給了我們一個外部網址,要我們去取得資料。
<form class="search-form">
<input type="text" class="search" placeholder="City or State" />
<ul class="suggestions">
<li>Filter for a city</li>
<li>or a state</li>
</ul>
</form>
const endpoint =
"https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json";
實作開始
getData
異步函式,去跟這個網站要求資料,如果Response: ok property為 true 則使用Response: json()將回傳的Response 物件視為 json 格式解析後再回傳。async function getData(endpoint) {
const response = await fetch(endpoint);
if (response.ok) return await response.json();
}
search
、suggestions
分別選取輸入框及清單的節點,並以Intl.NumberFormat()建構出一個數字格式設定存放於變數nf
中const search = document.querySelector(".search");
const suggestions = document.querySelector(".suggestions");
const nf = new Intl.NumberFormat();
main
於進入網頁後執行,(1).先宣告一個data
變數等待getData(endpoint)
的執行結果,待取得資料有結果後,(2)判斷 data 是否有值,如果有(3)才針對input
輸入框新增一個事件監聽:每當 input 值發生變動會時執行函式,(4)如果變動後的值是空字串則執行renderAllCities(data)
全部城市渲染的函式,然後結束,若不是空字串則(5)以輸入框的值(需捕獲)建構一個正則表達式規則,並設 g(全部搜索:RegExp.prototype.global)及 i不區分大小寫搜索 RegExp.prototype.ignoreCase旗標,(6)執行filterAndFormatCities(data, searchReg)
篩選符合的城市並映射將處理後的結果存放於變數matchesCities
中,最後執行renderMatchesCity(matchesCities, searchReg)
篩選後城市的渲染函式。main();
async function main() {
const data = await getData(endpoint);
if (data) {
search.addEventListener("input", (e) => {
if (e.target.value === "") return renderAllCities(data);
const searchReg = new RegExp(`(${e.target.value})`, "ig");
const matchesCities = filterAndFormatCities(data, searchReg);
renderMatchesCity(matchesCities, searchReg);
// console.log(matchesCities);
});
}
}
name:拼接城市全名➡️以搜尋字串當作切割點捕獲並切割成陣列➡️過濾掉空字串項
population:使用數字格式器轉換為含有千分位符的字串
function filterAndFormatCities(data, searchReg) {
return data
.filter((item) => searchReg.test(item.city) || searchReg.test(item.state))
.map((item) => {
return {
name: `${item.city}, ${item.state}`
.split(searchReg)
.filter((i) => i !== ""),
population: nf.format(item.population),
};
});
}
function renderMatchesCity(cities, searchReg) {
//創建文檔片段
const fragment = document.createDocumentFragment();
cities.forEach((city) => {
//單一城市結構<li>
const li = document.createElement("li");
// 城市全名<span>
const name = document.createElement("span");
name.classList.add("name");
//mapping過的資料的key[name]依據搜尋字串切片成陣列了這邊再foreEacht創造節點append入name這個sapn中
city.name.forEach((text) => {
const textSlice = document.createElement("span");
//這行是重點,再次判斷是搜尋字串的才掛有highlight樣式
if (searchReg.test(text)) textSlice.classList.add("hl");
textSlice.textContent = text;
name.appendChild(textSlice);
});
const population = document.createElement("span");
population.classList.add("population");
population.textContent = city.population;
//將兩個城市資料append進<li>結構中
li.appendChild(name);
li.appendChild(population);
//將單一城市<li>結構append進文檔片段中
fragment.appendChild(li);
});
//先清空<ul>清單內所有元素
while (suggestions.firstChild) {
suggestions.removeChild(suggestions.firstChild);
}
//將文檔片段append進清單中於畫面上實際渲染
suggestions.appendChild(fragment);
}
""
空字串去創造正規表達式去跑後面篩選及渲染邏輯,會因為 String.split(/""/)的關係,將所有字母都全部切片且視為符合篩選掛上 classhl
的高光<span>
結構,如下圖所以在一開始改為先判斷輸入值為空時則改執行這個全部城市的渲染函式,也不需將資料 mapping 處理,只要在設定 span.textContent 時利用字面模板串接原資料城市全名,及將人口數轉換千位分隔符字串即可。function renderAllCities(data) {
//創建文檔片段
const fragment = document.createDocumentFragment();
data.forEach((item) => {
//單一城市結構<li>
const li = document.createElement("li");
// 城市全名<span>
const name = document.createElement("span");
name.classList.add("name");
name.textContent = `${item.city}, ${item.state}`;
// 城市人口<span>
const population = document.createElement("span");
population.classList.add("population");
//城市人口數轉換千位分隔符字串
population.textContent = nf.format(item.population);
//將兩個城市資料append進<li>結構中
li.appendChild(name);
li.appendChild(population);
//將單一城市<li>結構append進文檔片段中
fragment.appendChild(li);
});
//先清空<ul>清單內所有元素
while (suggestions.firstChild) {
suggestions.removeChild(suggestions.firstChild);
}
//將文檔片段append進清單中於畫面上實際渲染
suggestions.appendChild(fragment);
}
終於完成今天的實作,我的寫法與作者的不同,因為看到innerHtML 的安全疑慮及不推薦使用,所以為了實現不使用 innerHTML 堅持使用創造元素、設定元素屬性、再渲染至畫面上的方式,整體程式碼相對複雜,匹配字樣也不能使用 replace 直接替換成 html 結構(但是存在著搜尋輸入大寫搜尋下方資料會跟著變更成大寫的 bug),而是需要所有字串都切成片段並創造元素插入的方式,但不會出現前述漏洞
左邊為原作者效果/右邊是我的效果